/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;

using Microsoft.Scripting.Ast;

namespace Microsoft.Scripting.Generation {
    internal sealed class ConstantPool {
        private List<object> _data;
        private Slot _dataSlot;
        private LambdaCompiler _cg;

        private static readonly List<object> _staticData = new List<object>();
        private static readonly object _nullVal = new object();
        [Microsoft.Scripting.Runtime.MultiRuntimeAware]
        private static int _lastCheck, _empties;

        private ConstantPool(List<object> data) {
            _data = data;
        }

        internal ConstantPool() {
            _data = new List<object>();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] // TODO: fix
        internal object[] Data {
            get { return _data.ToArray(); }
        }

        internal Slot Slot {
            get { return _dataSlot; }
        }

        internal int Count {
            get { return _data.Count; }
        }

        internal void SetCodeGen(LambdaCompiler cg, Slot dataSlot) {
            this._cg = cg;
            this._dataSlot = dataSlot;
        }

        internal Slot AddData(object data, Type type) {
            Slot result;

            // TODO: is GetVisibleType check here correct?
            // (we can't emit a cast in a DynamicMethod to a non-visible type,
            // even in full-trust, so it doesn't seem avoidable)
            type = CompilerHelpers.GetVisibleType(type);

            if (IsBound) {
                _data.Add(data);

                result = CastSlot.Cast(type, new IndexSlot(_dataSlot, _data.Count - 1));
            } else {                
                int index = AddStaticData(data);
                
                result = _cg.TypeGen.AddStaticField(type, "#SlotStorage" + index.ToString());

            }
            return result;
        }

        /// <summary>
        /// True if the ConstantPool will be bound as the 1st argument to a delegate.  False
        /// if the constant pool storage will always go into static fields.
        /// </summary>
        internal bool IsBound {
            get {
                return _dataSlot != null;
            }
        }

        internal ConstantPool CopyData() {
            return new ConstantPool(_data);
        }

        private static int AddStaticData(object data) {
            lock (_staticData) {
                if (_empties != 0) {
                    while(_lastCheck < _staticData.Count) {
                        if (_staticData[_lastCheck] == null) {
                            _staticData[_lastCheck] = data == null ? _nullVal : data;
                            _empties--;
                            return _lastCheck;
                        }
                        _lastCheck++;
                    }
                }

                _lastCheck = 0;
                _staticData.Add(data == null ? _nullVal : data);
                return _staticData.Count - 1;
            }
        }

        internal static object GetConstantData(int index) {
            lock (_staticData) {
                object res = _staticData[index];
                _staticData[index] = null;
                _empties++;
                Debug.Assert(res != null);
                return res == _nullVal ? null : res;
            }
        }

        internal static object GetConstantDataReusable(int index) {
            lock (_staticData) {
                object res = _staticData[index];
                Debug.Assert(res != null);
                return res == _nullVal ? null : res;
            }
        }
    }
}
